/*
* Copyright 2011 Red Hat, Inc. and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.optaplanner.benchmark.impl.report;
import java.awt.BasicStroke;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.text.NumberFormat;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.NavigableSet;
import java.util.TreeSet;
import javax.imageio.ImageIO;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.CategoryAxis;
import org.jfree.chart.axis.LogarithmicAxis;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.labels.ItemLabelAnchor;
import org.jfree.chart.labels.ItemLabelPosition;
import org.jfree.chart.labels.StandardCategoryItemLabelGenerator;
import org.jfree.chart.plot.CategoryPlot;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.category.BarRenderer;
import org.jfree.chart.renderer.category.BoxAndWhiskerRenderer;
import org.jfree.chart.renderer.xy.StandardXYItemRenderer;
import org.jfree.chart.renderer.xy.XYItemRenderer;
import org.jfree.data.category.DefaultCategoryDataset;
import org.jfree.data.statistics.DefaultBoxAndWhiskerCategoryDataset;
import org.jfree.data.xy.XYDataItem;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;
import org.jfree.ui.TextAnchor;
import org.optaplanner.benchmark.impl.ranking.SolverRankingWeightFactory;
import org.optaplanner.benchmark.impl.result.PlannerBenchmarkResult;
import org.optaplanner.benchmark.impl.result.ProblemBenchmarkResult;
import org.optaplanner.benchmark.impl.result.SingleBenchmarkResult;
import org.optaplanner.benchmark.impl.result.SolverBenchmarkResult;
import org.optaplanner.benchmark.impl.result.SubSingleBenchmarkResult;
import org.optaplanner.benchmark.impl.statistic.ProblemStatistic;
import org.optaplanner.benchmark.impl.statistic.PureSubSingleStatistic;
import org.optaplanner.benchmark.impl.statistic.SubSingleStatistic;
import org.optaplanner.benchmark.impl.statistic.common.MillisecondsSpentNumberFormat;
import org.optaplanner.core.config.solver.EnvironmentMode;
import org.optaplanner.core.impl.score.ScoreUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class BenchmarkReport {
protected final transient Logger logger = LoggerFactory.getLogger(getClass());
public static final int CHARTED_SCORE_LEVEL_SIZE = 15;
public static final int LOG_SCALE_MIN_DATASETS_COUNT = 5;
private final PlannerBenchmarkResult plannerBenchmarkResult;
private Locale locale = null;
private ZoneId timezoneId = null;
private Comparator<SolverBenchmarkResult> solverRankingComparator = null;
private SolverRankingWeightFactory solverRankingWeightFactory = null;
private File summaryDirectory = null;
private List<File> bestScoreSummaryChartFileList = null;
private List<File> bestScoreScalabilitySummaryChartFileList = null;
private List<File> bestScoreDistributionSummaryChartFileList = null;
private List<File> winningScoreDifferenceSummaryChartFileList = null;
private List<File> worstScoreDifferencePercentageSummaryChartFileList = null;
private File scoreCalculationSpeedSummaryChartFile = null;
private File worstScoreCalculationSpeedDifferencePercentageSummaryChartFile = null;
private File timeSpentSummaryChartFile = null;
private File timeSpentScalabilitySummaryChartFile = null;
private List<File> bestScorePerTimeSpentSummaryChartFileList = null;
private Integer defaultShownScoreLevelIndex = null;
private List<String> warningList = null;
private File htmlOverviewFile = null;
public BenchmarkReport(PlannerBenchmarkResult plannerBenchmarkResult) {
this.plannerBenchmarkResult = plannerBenchmarkResult;
}
public PlannerBenchmarkResult getPlannerBenchmarkResult() {
return plannerBenchmarkResult;
}
public Locale getLocale() {
return locale;
}
public void setLocale(Locale locale) {
this.locale = locale;
}
public ZoneId getTimezoneId() {
return timezoneId;
}
public void setTimezoneId(ZoneId timezoneId) {
this.timezoneId = timezoneId;
}
public Comparator<SolverBenchmarkResult> getSolverRankingComparator() {
return solverRankingComparator;
}
public void setSolverRankingComparator(Comparator<SolverBenchmarkResult> solverRankingComparator) {
this.solverRankingComparator = solverRankingComparator;
}
public SolverRankingWeightFactory getSolverRankingWeightFactory() {
return solverRankingWeightFactory;
}
public void setSolverRankingWeightFactory(SolverRankingWeightFactory solverRankingWeightFactory) {
this.solverRankingWeightFactory = solverRankingWeightFactory;
}
public File getSummaryDirectory() {
return summaryDirectory;
}
public List<File> getBestScoreSummaryChartFileList() {
return bestScoreSummaryChartFileList;
}
public List<File> getBestScoreScalabilitySummaryChartFileList() {
return bestScoreScalabilitySummaryChartFileList;
}
public List<File> getBestScoreDistributionSummaryChartFileList() {
return bestScoreDistributionSummaryChartFileList;
}
public List<File> getWinningScoreDifferenceSummaryChartFileList() {
return winningScoreDifferenceSummaryChartFileList;
}
public List<File> getWorstScoreDifferencePercentageSummaryChartFileList() {
return worstScoreDifferencePercentageSummaryChartFileList;
}
public File getScoreCalculationSpeedSummaryChartFile() {
return scoreCalculationSpeedSummaryChartFile;
}
public File getWorstScoreCalculationSpeedDifferencePercentageSummaryChartFile() {
return worstScoreCalculationSpeedDifferencePercentageSummaryChartFile;
}
public File getTimeSpentSummaryChartFile() {
return timeSpentSummaryChartFile;
}
public File getTimeSpentScalabilitySummaryChartFile() {
return timeSpentScalabilitySummaryChartFile;
}
public List<File> getBestScorePerTimeSpentSummaryChartFileList() {
return bestScorePerTimeSpentSummaryChartFileList;
}
public Integer getDefaultShownScoreLevelIndex() {
return defaultShownScoreLevelIndex;
}
public List<String> getWarningList() {
return warningList;
}
public File getHtmlOverviewFile() {
return htmlOverviewFile;
}
// ************************************************************************
// Smart getters
// ************************************************************************
public String getRelativePathToBenchmarkReportDirectory(File file) {
String benchmarkReportDirectoryPath = plannerBenchmarkResult.getBenchmarkReportDirectory().getAbsoluteFile().toURI().getPath();
String filePath = file.getAbsoluteFile().toURI().getPath();
if (!filePath.startsWith(benchmarkReportDirectoryPath)) {
throw new IllegalArgumentException("The filePath (" + filePath
+ ") does not start with the benchmarkReportDirectoryPath (" + benchmarkReportDirectoryPath + ").");
}
String relativePath = filePath.substring(benchmarkReportDirectoryPath.length());
if (relativePath.startsWith("/")) {
relativePath = relativePath.substring(1);
}
return relativePath;
}
public String getSolverRankingClassSimpleName() {
Class solverRankingClass = getSolverRankingClass();
return solverRankingClass == null ? null : solverRankingClass.getSimpleName();
}
public String getSolverRankingClassFullName() {
Class solverRankingClass = getSolverRankingClass();
return solverRankingClass == null ? null : solverRankingClass.getName();
}
// ************************************************************************
// Write methods
// ************************************************************************
public void writeReport() {
logger.info("Generating benchmark report...");
summaryDirectory = new File(plannerBenchmarkResult.getBenchmarkReportDirectory(), "summary");
summaryDirectory.mkdir();
plannerBenchmarkResult.accumulateResults(this);
fillWarningList();
writeBestScoreSummaryChart();
writeBestScoreScalabilitySummaryChart();
writeWinningScoreDifferenceSummaryChart();
writeWorstScoreDifferencePercentageSummaryChart();
writeBestScoreDistributionSummaryChart();
writeScoreCalculationSpeedSummaryChart();
writeWorstScoreCalculationSpeedDifferencePercentageSummaryChart();
writeTimeSpentSummaryChart();
writeTimeSpentScalabilitySummaryChart();
writeBestScorePerTimeSpentSummaryChart();
for (ProblemBenchmarkResult<Object> problemBenchmarkResult : plannerBenchmarkResult.getUnifiedProblemBenchmarkResultList()) {
for (SingleBenchmarkResult singleBenchmarkResult : problemBenchmarkResult.getSingleBenchmarkResultList()) {
for (SubSingleBenchmarkResult subSingleBenchmarkResult : singleBenchmarkResult.getSubSingleBenchmarkResultList()) {
if (!subSingleBenchmarkResult.hasAllSuccess()) {
continue;
}
for (SubSingleStatistic subSingleStatistic : subSingleBenchmarkResult.getEffectiveSubSingleStatisticMap().values()) {
try {
subSingleStatistic.unhibernatePointList();
} catch (IllegalStateException e) {
if (!plannerBenchmarkResult.getAggregation()) {
throw new IllegalStateException("Failed to unhibernate point list of SubSingleStatistic ("
+ subSingleStatistic + ") of SubSingleBenchmark (" + subSingleBenchmarkResult + ").", e);
}
logger.trace("This is expected, aggregator doesn't copy CSV files. Could not read CSV file "
+ "({}) of sub single statistic ({}).", subSingleStatistic.getCsvFile().getAbsolutePath(), subSingleStatistic);
}
}
}
}
}
for (ProblemBenchmarkResult<Object> problemBenchmarkResult : plannerBenchmarkResult.getUnifiedProblemBenchmarkResultList()) {
if (problemBenchmarkResult.hasAnySuccess()) {
for (ProblemStatistic problemStatistic : problemBenchmarkResult.getProblemStatisticList()) {
problemStatistic.writeGraphFiles(this);
}
for (SingleBenchmarkResult singleBenchmarkResult : problemBenchmarkResult.getSingleBenchmarkResultList()) {
if (singleBenchmarkResult.hasAllSuccess()) {
for (PureSubSingleStatistic pureSubSingleStatistic : singleBenchmarkResult.getMedian().getPureSubSingleStatisticList()) {
pureSubSingleStatistic.writeGraphFiles(this);
}
}
}
}
}
for (ProblemBenchmarkResult<Object> problemBenchmarkResult : plannerBenchmarkResult.getUnifiedProblemBenchmarkResultList()) {
for (SingleBenchmarkResult singleBenchmarkResult : problemBenchmarkResult.getSingleBenchmarkResultList()) {
for (SubSingleBenchmarkResult subSingleBenchmarkResult : singleBenchmarkResult.getSubSingleBenchmarkResultList()) {
if (!subSingleBenchmarkResult.hasAllSuccess()) {
continue;
}
for (SubSingleStatistic subSingleStatistic : subSingleBenchmarkResult.getEffectiveSubSingleStatisticMap().values()) {
if (plannerBenchmarkResult.getAggregation()) {
subSingleStatistic.setPointList(null);
} else {
subSingleStatistic.hibernatePointList();
}
}
}
}
}
determineDefaultShownScoreLevelIndex();
writeHtmlOverviewFile();
}
protected void fillWarningList() {
warningList = new ArrayList<>();
String javaVmName = System.getProperty("java.vm.name");
if (javaVmName != null && javaVmName.contains("Client VM")) {
warningList.add("The Java VM (" + javaVmName + ") is the Client VM."
+ " This decreases performance."
+ " Maybe start the java process with the argument \"-server\" to get better results.");
}
Integer parallelBenchmarkCount = plannerBenchmarkResult.getParallelBenchmarkCount();
Integer availableProcessors = plannerBenchmarkResult.getAvailableProcessors();
if (parallelBenchmarkCount != null && availableProcessors != null
&& parallelBenchmarkCount > availableProcessors) {
warningList.add("The parallelBenchmarkCount (" + parallelBenchmarkCount
+ ") is higher than the number of availableProcessors (" + availableProcessors + ")."
+ " This decreases performance."
+ " Maybe reduce the parallelBenchmarkCount.");
}
EnvironmentMode environmentMode = plannerBenchmarkResult.getEnvironmentMode();
if (environmentMode != null && environmentMode.isAsserted()) {
warningList.add("The environmentMode (" + environmentMode + ") is asserting."
+ " This decreases performance."
+ " Maybe set the environmentMode to " + EnvironmentMode.REPRODUCIBLE + ".");
}
String loggingLevelOptaPlannerCore = plannerBenchmarkResult.getLoggingLevelOptaPlannerCore();
if (loggingLevelOptaPlannerCore != null && loggingLevelOptaPlannerCore.equals("trace")) {
warningList.add("The loggingLevel (" + loggingLevelOptaPlannerCore + ") of org.optaplanner.core is high."
+ " This decreases performance."
+ " Maybe set the loggingLevel to debug or lower.");
}
String loggingLevelDroolsCore = plannerBenchmarkResult.getLoggingLevelDroolsCore();
if (loggingLevelDroolsCore != null
&& (loggingLevelDroolsCore.equals("trace") || loggingLevelDroolsCore.equals("debug"))) {
warningList.add("The loggingLevel (" + loggingLevelDroolsCore + ") of org.drools.core is high."
+ " This decreases performance."
+ " Maybe set the loggingLevel to info or lower.");
}
}
private void writeBestScoreSummaryChart() {
// Each scoreLevel has it's own dataset and chartFile
List<DefaultCategoryDataset> datasetList = new ArrayList<>(CHARTED_SCORE_LEVEL_SIZE);
for (SolverBenchmarkResult solverBenchmarkResult : plannerBenchmarkResult.getSolverBenchmarkResultList()) {
String solverLabel = solverBenchmarkResult.getNameWithFavoriteSuffix();
for (SingleBenchmarkResult singleBenchmarkResult : solverBenchmarkResult.getSingleBenchmarkResultList()) {
String planningProblemLabel = singleBenchmarkResult.getProblemBenchmarkResult().getName();
if (singleBenchmarkResult.hasAllSuccess()) {
double[] levelValues = ScoreUtils.extractLevelDoubles(singleBenchmarkResult.getAverageScore());
for (int i = 0; i < levelValues.length && i < CHARTED_SCORE_LEVEL_SIZE; i++) {
if (i >= datasetList.size()) {
datasetList.add(new DefaultCategoryDataset());
}
datasetList.get(i).addValue(levelValues[i], solverLabel, planningProblemLabel);
}
}
}
}
bestScoreSummaryChartFileList = new ArrayList<>(datasetList.size());
int scoreLevelIndex = 0;
for (DefaultCategoryDataset dataset : datasetList) {
String scoreLevelLabel = plannerBenchmarkResult.findScoreLevelLabel(scoreLevelIndex);
CategoryPlot plot = createBarChartPlot(dataset,
"Best " + scoreLevelLabel, NumberFormat.getInstance(locale));
JFreeChart chart = new JFreeChart("Best " + scoreLevelLabel + " summary (higher is better)",
JFreeChart.DEFAULT_TITLE_FONT, plot, true);
bestScoreSummaryChartFileList.add(writeChartToImageFile(chart, "bestScoreSummaryLevel" + scoreLevelIndex));
scoreLevelIndex++;
}
}
private void writeBestScoreScalabilitySummaryChart() {
// Each scoreLevel has it's own dataset and chartFile
List<List<XYSeries>> seriesListList = new ArrayList<>(
CHARTED_SCORE_LEVEL_SIZE);
int solverBenchmarkIndex = 0;
for (SolverBenchmarkResult solverBenchmarkResult : plannerBenchmarkResult.getSolverBenchmarkResultList()) {
String solverLabel = solverBenchmarkResult.getNameWithFavoriteSuffix();
for (SingleBenchmarkResult singleBenchmarkResult : solverBenchmarkResult.getSingleBenchmarkResultList()) {
if (singleBenchmarkResult.hasAllSuccess()) {
long problemScale = singleBenchmarkResult.getProblemBenchmarkResult().getProblemScale();
double[] levelValues = ScoreUtils.extractLevelDoubles(singleBenchmarkResult.getAverageScore());
for (int i = 0; i < levelValues.length && i < CHARTED_SCORE_LEVEL_SIZE; i++) {
if (i >= seriesListList.size()) {
seriesListList.add(new ArrayList<>(
plannerBenchmarkResult.getSolverBenchmarkResultList().size()));
}
List<XYSeries> seriesList = seriesListList.get(i);
while (solverBenchmarkIndex >= seriesList.size()) {
seriesList.add(new XYSeries(solverLabel));
}
seriesList.get(solverBenchmarkIndex).add((double) problemScale, levelValues[i]);
}
}
}
solverBenchmarkIndex++;
}
bestScoreScalabilitySummaryChartFileList = new ArrayList<>(seriesListList.size());
int scoreLevelIndex = 0;
for (List<XYSeries> seriesList : seriesListList) {
String scoreLevelLabel = plannerBenchmarkResult.findScoreLevelLabel(scoreLevelIndex);
XYPlot plot = createScalabilityPlot(seriesList,
"Problem scale", NumberFormat.getInstance(locale),
"Best " + scoreLevelLabel, NumberFormat.getInstance(locale));
JFreeChart chart = new JFreeChart(
"Best " + scoreLevelLabel + " scalability summary (higher is better)",
JFreeChart.DEFAULT_TITLE_FONT, plot, true);
bestScoreScalabilitySummaryChartFileList.add(
writeChartToImageFile(chart, "bestScoreScalabilitySummaryLevel" + scoreLevelIndex));
scoreLevelIndex++;
}
}
private void writeBestScoreDistributionSummaryChart() {
// Each scoreLevel has it's own dataset and chartFile
List<DefaultBoxAndWhiskerCategoryDataset> datasetList = new ArrayList<>(CHARTED_SCORE_LEVEL_SIZE);
for (SolverBenchmarkResult solverBenchmarkResult : plannerBenchmarkResult.getSolverBenchmarkResultList()) {
String solverLabel = solverBenchmarkResult.getNameWithFavoriteSuffix();
for (SingleBenchmarkResult singleBenchmarkResult : solverBenchmarkResult.getSingleBenchmarkResultList()) {
String planningProblemLabel = singleBenchmarkResult.getProblemBenchmarkResult().getName();
if (singleBenchmarkResult.hasAllSuccess()) {
List<List<Double>> distributionLevelList = new ArrayList<>(CHARTED_SCORE_LEVEL_SIZE);
for (SubSingleBenchmarkResult subSingleBenchmarkResult : singleBenchmarkResult.getSubSingleBenchmarkResultList()) {
double[] levelValues = ScoreUtils.extractLevelDoubles(subSingleBenchmarkResult.getAverageScore());
for (int i = 0; i < levelValues.length && i < CHARTED_SCORE_LEVEL_SIZE; i++) {
if (i >= distributionLevelList.size()) {
distributionLevelList.add(new ArrayList<>(singleBenchmarkResult.getSubSingleCount()));
}
distributionLevelList.get(i).add(levelValues[i]);
}
}
for (int i = 0; i < distributionLevelList.size() && i < CHARTED_SCORE_LEVEL_SIZE; i++) {
if (i >= datasetList.size()) {
datasetList.add(new DefaultBoxAndWhiskerCategoryDataset());
}
datasetList.get(i).add(
distributionLevelList.get(i),
solverLabel,
planningProblemLabel);
}
}
}
}
bestScoreDistributionSummaryChartFileList = new ArrayList<>(datasetList.size());
int scoreLevelIndex = 0;
for (DefaultBoxAndWhiskerCategoryDataset dataset : datasetList) {
String scoreLevelLabel = plannerBenchmarkResult.findScoreLevelLabel(scoreLevelIndex);
CategoryPlot plot = createBoxAndWhiskerChartPlot(dataset,
"Best " + scoreLevelLabel, NumberFormat.getInstance(locale));
JFreeChart chart = new JFreeChart("Best " + scoreLevelLabel + " distribution summary (higher is better)",
JFreeChart.DEFAULT_TITLE_FONT, plot, true);
bestScoreDistributionSummaryChartFileList.add(writeChartToImageFile(chart, "bestScoreDistributionSummaryLevel" + scoreLevelIndex));
scoreLevelIndex++;
}
}
private void writeWinningScoreDifferenceSummaryChart() {
// Each scoreLevel has it's own dataset and chartFile
List<DefaultCategoryDataset> datasetList = new ArrayList<>(CHARTED_SCORE_LEVEL_SIZE);
for (SolverBenchmarkResult solverBenchmarkResult : plannerBenchmarkResult.getSolverBenchmarkResultList()) {
String solverLabel = solverBenchmarkResult.getNameWithFavoriteSuffix();
for (SingleBenchmarkResult singleBenchmarkResult : solverBenchmarkResult.getSingleBenchmarkResultList()) {
String planningProblemLabel = singleBenchmarkResult.getProblemBenchmarkResult().getName();
if (singleBenchmarkResult.hasAllSuccess()) {
double[] levelValues = ScoreUtils.extractLevelDoubles(singleBenchmarkResult.getWinningScoreDifference());
for (int i = 0; i < levelValues.length && i < CHARTED_SCORE_LEVEL_SIZE; i++) {
if (i >= datasetList.size()) {
datasetList.add(new DefaultCategoryDataset());
}
datasetList.get(i).addValue(levelValues[i], solverLabel, planningProblemLabel);
}
}
}
}
winningScoreDifferenceSummaryChartFileList = new ArrayList<>(datasetList.size());
int scoreLevelIndex = 0;
for (DefaultCategoryDataset dataset : datasetList) {
String scoreLevelLabel = plannerBenchmarkResult.findScoreLevelLabel(scoreLevelIndex);
CategoryPlot plot = createBarChartPlot(dataset,
"Winning " + scoreLevelLabel + " difference", NumberFormat.getInstance(locale));
JFreeChart chart = new JFreeChart("Winning " + scoreLevelLabel + " difference summary (higher is better)",
JFreeChart.DEFAULT_TITLE_FONT, plot, true);
winningScoreDifferenceSummaryChartFileList.add(
writeChartToImageFile(chart, "winningScoreDifferenceSummaryLevel" + scoreLevelIndex));
scoreLevelIndex++;
}
}
private void writeWorstScoreDifferencePercentageSummaryChart() {
// Each scoreLevel has it's own dataset and chartFile
List<DefaultCategoryDataset> datasetList = new ArrayList<>(CHARTED_SCORE_LEVEL_SIZE);
for (SolverBenchmarkResult solverBenchmarkResult : plannerBenchmarkResult.getSolverBenchmarkResultList()) {
String solverLabel = solverBenchmarkResult.getNameWithFavoriteSuffix();
for (SingleBenchmarkResult singleBenchmarkResult : solverBenchmarkResult.getSingleBenchmarkResultList()) {
String planningProblemLabel = singleBenchmarkResult.getProblemBenchmarkResult().getName();
if (singleBenchmarkResult.hasAllSuccess()) {
double[] levelValues = singleBenchmarkResult.getWorstScoreDifferencePercentage().getPercentageLevels();
for (int i = 0; i < levelValues.length && i < CHARTED_SCORE_LEVEL_SIZE; i++) {
if (i >= datasetList.size()) {
datasetList.add(new DefaultCategoryDataset());
}
datasetList.get(i).addValue(levelValues[i], solverLabel, planningProblemLabel);
}
}
}
}
worstScoreDifferencePercentageSummaryChartFileList = new ArrayList<>(datasetList.size());
int scoreLevelIndex = 0;
for (DefaultCategoryDataset dataset : datasetList) {
String scoreLevelLabel = plannerBenchmarkResult.findScoreLevelLabel(scoreLevelIndex);
CategoryPlot plot = createBarChartPlot(dataset,
"Worst " + scoreLevelLabel + " difference percentage",
NumberFormat.getPercentInstance(locale));
JFreeChart chart = new JFreeChart("Worst " + scoreLevelLabel + " difference percentage"
+ " summary (higher is better)",
JFreeChart.DEFAULT_TITLE_FONT, plot, true);
worstScoreDifferencePercentageSummaryChartFileList.add(
writeChartToImageFile(chart, "worstScoreDifferencePercentageSummaryLevel" + scoreLevelIndex));
scoreLevelIndex++;
}
}
private void writeScoreCalculationSpeedSummaryChart() {
List<XYSeries> seriesList = new ArrayList<>(plannerBenchmarkResult.getSolverBenchmarkResultList().size());
for (SolverBenchmarkResult solverBenchmarkResult : plannerBenchmarkResult.getSolverBenchmarkResultList()) {
String solverLabel = solverBenchmarkResult.getNameWithFavoriteSuffix();
XYSeries series = new XYSeries(solverLabel);
for (SingleBenchmarkResult singleBenchmarkResult : solverBenchmarkResult.getSingleBenchmarkResultList()) {
if (singleBenchmarkResult.hasAllSuccess()) {
long problemScale = singleBenchmarkResult.getProblemBenchmarkResult().getProblemScale();
long scoreCalculationSpeed = singleBenchmarkResult.getScoreCalculationSpeed();
series.add((Long) problemScale, (Long) scoreCalculationSpeed);
}
}
seriesList.add(series);
}
XYPlot plot = createScalabilityPlot(seriesList,
"Problem scale", NumberFormat.getInstance(locale),
"Score calculation speed per second", NumberFormat.getInstance(locale));
JFreeChart chart = new JFreeChart("Score calculation speed summary (higher is better)",
JFreeChart.DEFAULT_TITLE_FONT, plot, true);
scoreCalculationSpeedSummaryChartFile = writeChartToImageFile(chart, "scoreCalculationSpeedSummary");
}
private void writeWorstScoreCalculationSpeedDifferencePercentageSummaryChart() {
DefaultCategoryDataset dataset = new DefaultCategoryDataset();
for (SolverBenchmarkResult solverBenchmarkResult : plannerBenchmarkResult.getSolverBenchmarkResultList()) {
String solverLabel = solverBenchmarkResult.getNameWithFavoriteSuffix();
for (SingleBenchmarkResult singleBenchmarkResult : solverBenchmarkResult.getSingleBenchmarkResultList()) {
String planningProblemLabel = singleBenchmarkResult.getProblemBenchmarkResult().getName();
if (singleBenchmarkResult.hasAllSuccess()) {
double worstScoreCalculationSpeedDifferencePercentage
= singleBenchmarkResult.getWorstScoreCalculationSpeedDifferencePercentage();
dataset.addValue(worstScoreCalculationSpeedDifferencePercentage, solverLabel, planningProblemLabel);
}
}
}
CategoryPlot plot = createBarChartPlot(dataset,
"Worst score calculation speed difference percentage",
NumberFormat.getPercentInstance(locale));
JFreeChart chart = new JFreeChart("Worst score calculation speed difference percentage"
+ " summary (higher is better)",
JFreeChart.DEFAULT_TITLE_FONT, plot, true);
worstScoreCalculationSpeedDifferencePercentageSummaryChartFile = writeChartToImageFile(chart, "worstScoreCalculationSpeedDifferencePercentageSummary");
}
private void writeTimeSpentSummaryChart() {
DefaultCategoryDataset dataset = new DefaultCategoryDataset();
for (SolverBenchmarkResult solverBenchmarkResult : plannerBenchmarkResult.getSolverBenchmarkResultList()) {
String solverLabel = solverBenchmarkResult.getNameWithFavoriteSuffix();
for (SingleBenchmarkResult singleBenchmarkResult : solverBenchmarkResult.getSingleBenchmarkResultList()) {
String planningProblemLabel = singleBenchmarkResult.getProblemBenchmarkResult().getName();
if (singleBenchmarkResult.hasAllSuccess()) {
long timeMillisSpent = singleBenchmarkResult.getTimeMillisSpent();
dataset.addValue(timeMillisSpent, solverLabel, planningProblemLabel);
}
}
}
CategoryPlot plot = createBarChartPlot(dataset, "Time spent", new MillisecondsSpentNumberFormat(locale));
JFreeChart chart = new JFreeChart("Time spent summary (lower time is better)",
JFreeChart.DEFAULT_TITLE_FONT, plot, true);
timeSpentSummaryChartFile = writeChartToImageFile(chart, "timeSpentSummary");
}
private void writeTimeSpentScalabilitySummaryChart() {
List<XYSeries> seriesList = new ArrayList<>(plannerBenchmarkResult.getSolverBenchmarkResultList().size());
for (SolverBenchmarkResult solverBenchmarkResult : plannerBenchmarkResult.getSolverBenchmarkResultList()) {
String solverLabel = solverBenchmarkResult.getNameWithFavoriteSuffix();
XYSeries series = new XYSeries(solverLabel);
for (SingleBenchmarkResult singleBenchmarkResult : solverBenchmarkResult.getSingleBenchmarkResultList()) {
if (singleBenchmarkResult.hasAllSuccess()) {
long problemScale = singleBenchmarkResult.getProblemBenchmarkResult().getProblemScale();
long timeMillisSpent = singleBenchmarkResult.getTimeMillisSpent();
series.add((Long) problemScale, (Long) timeMillisSpent);
}
}
seriesList.add(series);
}
XYPlot plot = createScalabilityPlot(seriesList,
"Problem scale", NumberFormat.getInstance(locale),
"Time spent", new MillisecondsSpentNumberFormat(locale));
JFreeChart chart = new JFreeChart("Time spent scalability summary (lower is better)",
JFreeChart.DEFAULT_TITLE_FONT, plot, true);
timeSpentScalabilitySummaryChartFile = writeChartToImageFile(chart, "timeSpentScalabilitySummary");
}
private void writeBestScorePerTimeSpentSummaryChart() {
// Each scoreLevel has it's own dataset and chartFile
List<List<XYSeries>> seriesListList = new ArrayList<>(
CHARTED_SCORE_LEVEL_SIZE);
int solverBenchmarkIndex = 0;
for (SolverBenchmarkResult solverBenchmarkResult : plannerBenchmarkResult.getSolverBenchmarkResultList()) {
String solverLabel = solverBenchmarkResult.getNameWithFavoriteSuffix();
for (SingleBenchmarkResult singleBenchmarkResult : solverBenchmarkResult.getSingleBenchmarkResultList()) {
if (singleBenchmarkResult.hasAllSuccess()) {
long timeMillisSpent = singleBenchmarkResult.getTimeMillisSpent();
double[] levelValues = ScoreUtils.extractLevelDoubles(singleBenchmarkResult.getAverageScore());
for (int i = 0; i < levelValues.length && i < CHARTED_SCORE_LEVEL_SIZE; i++) {
if (i >= seriesListList.size()) {
seriesListList.add(new ArrayList<>(
plannerBenchmarkResult.getSolverBenchmarkResultList().size()));
}
List<XYSeries> seriesList = seriesListList.get(i);
while (solverBenchmarkIndex >= seriesList.size()) {
seriesList.add(new XYSeries(solverLabel));
}
seriesList.get(solverBenchmarkIndex).add((Long) timeMillisSpent, (Double) levelValues[i]);
}
}
}
solverBenchmarkIndex++;
}
bestScorePerTimeSpentSummaryChartFileList = new ArrayList<>(seriesListList.size());
int scoreLevelIndex = 0;
for (List<XYSeries> seriesList : seriesListList) {
String scoreLevelLabel = plannerBenchmarkResult.findScoreLevelLabel(scoreLevelIndex);
XYPlot plot = createScalabilityPlot(seriesList,
"Time spent", new MillisecondsSpentNumberFormat(locale),
"Best " + scoreLevelLabel, NumberFormat.getInstance(locale));
JFreeChart chart = new JFreeChart(
"Best " + scoreLevelLabel + " per time spent summary (higher left is better)",
JFreeChart.DEFAULT_TITLE_FONT, plot, true);
bestScorePerTimeSpentSummaryChartFileList.add(
writeChartToImageFile(chart, "bestScorePerTimeSpentSummaryLevel" + scoreLevelIndex));
scoreLevelIndex++;
}
}
// ************************************************************************
// Chart helper methods
// ************************************************************************
private CategoryPlot createBarChartPlot(DefaultCategoryDataset dataset,
String yAxisLabel, NumberFormat yAxisNumberFormat) {
CategoryAxis xAxis = new CategoryAxis("Data");
xAxis.setCategoryMargin(0.40);
NumberAxis yAxis = new NumberAxis(yAxisLabel);
yAxis.setNumberFormatOverride(yAxisNumberFormat);
BarRenderer renderer = createBarChartRenderer(yAxisNumberFormat);
CategoryPlot plot = new CategoryPlot(dataset, xAxis, yAxis, renderer);
plot.setOrientation(PlotOrientation.VERTICAL);
return plot;
}
private BarRenderer createBarChartRenderer(NumberFormat numberFormat) {
BarRenderer renderer = new BarRenderer();
ItemLabelPosition positiveItemLabelPosition = new ItemLabelPosition(
ItemLabelAnchor.OUTSIDE12, TextAnchor.BOTTOM_CENTER);
renderer.setBasePositiveItemLabelPosition(positiveItemLabelPosition);
ItemLabelPosition negativeItemLabelPosition = new ItemLabelPosition(
ItemLabelAnchor.OUTSIDE6, TextAnchor.TOP_CENTER);
renderer.setBaseNegativeItemLabelPosition(negativeItemLabelPosition);
renderer.setBaseItemLabelGenerator(new StandardCategoryItemLabelGenerator(
StandardCategoryItemLabelGenerator.DEFAULT_LABEL_FORMAT_STRING, numberFormat));
renderer.setBaseItemLabelsVisible(true);
return renderer;
}
private XYPlot createScalabilityPlot(List<XYSeries> seriesList,
String xAxisLabel, NumberFormat xAxisNumberFormat,
String yAxisLabel, NumberFormat yAxisNumberFormat) {
NumberAxis xAxis;
if (useLogarithmicProblemScale(seriesList)) {
LogarithmicAxis logarithmicAxis = new LogarithmicAxis(xAxisLabel + " (logarithmic)");
logarithmicAxis.setAllowNegativesFlag(true);
xAxis = logarithmicAxis;
} else {
xAxis = new NumberAxis(xAxisLabel);
}
xAxis.setNumberFormatOverride(xAxisNumberFormat);
NumberAxis yAxis = new NumberAxis(yAxisLabel);
yAxis.setNumberFormatOverride(yAxisNumberFormat);
XYPlot plot = new XYPlot(null, xAxis, yAxis, null);
int seriesIndex = 0;
for (XYSeries series : seriesList) {
XYSeriesCollection seriesCollection = new XYSeriesCollection();
seriesCollection.addSeries(series);
plot.setDataset(seriesIndex, seriesCollection);
XYItemRenderer renderer = createScalabilityPlotRenderer(yAxisNumberFormat);
plot.setRenderer(seriesIndex, renderer);
seriesIndex++;
}
plot.setOrientation(PlotOrientation.VERTICAL);
return plot;
}
protected boolean useLogarithmicProblemScale(List<XYSeries> seriesList) {
NavigableSet<Double> xValueSet = new TreeSet<>();
int xValueListSize = 0;
for (XYSeries series : seriesList) {
for (XYDataItem dataItem : (List<XYDataItem>) series.getItems()) {
xValueSet.add(dataItem.getXValue());
xValueListSize++;
}
}
if (xValueListSize < LOG_SCALE_MIN_DATASETS_COUNT) {
return false;
}
// If 60% of the points are in 20% of the value space, use a logarithmic scale
double threshold = 0.2 * (xValueSet.last() - xValueSet.first());
int belowThresholdCount = xValueSet.headSet(threshold).size();
return belowThresholdCount >= (0.6 * xValueSet.size());
}
private XYItemRenderer createScalabilityPlotRenderer(NumberFormat numberFormat) {
XYItemRenderer renderer = new StandardXYItemRenderer(StandardXYItemRenderer.SHAPES_AND_LINES);
// Use dashed line
renderer.setSeriesStroke(0, new BasicStroke(
1.0f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 1.0f, new float[] {2.0f, 6.0f}, 0.0f
));
return renderer;
}
private CategoryPlot createBoxAndWhiskerChartPlot(DefaultBoxAndWhiskerCategoryDataset dataset,
String yAxisLabel, NumberFormat yAxisNumberFormat) {
CategoryAxis xAxis = new CategoryAxis("Data");
NumberAxis yAxis = new NumberAxis(yAxisLabel);
yAxis.setNumberFormatOverride(yAxisNumberFormat);
BoxAndWhiskerRenderer renderer = new BoxAndWhiskerRenderer();
renderer.setItemMargin(0.10);
renderer.setMeanVisible(false);
// Improve readability by avoiding low contrast with light colors
renderer.setUseOutlinePaintForWhiskers(true);
CategoryPlot plot = new CategoryPlot(dataset, xAxis, yAxis, renderer);
plot.setOrientation(PlotOrientation.VERTICAL);
return plot;
}
private File writeChartToImageFile(JFreeChart chart, String fileNameBase) {
BufferedImage chartImage = chart.createBufferedImage(1024, 768);
File summaryChartFile = new File(summaryDirectory, fileNameBase + ".png");
try (OutputStream out = new FileOutputStream(summaryChartFile)) {
ImageIO.write(chartImage, "png", out);
} catch (IOException e) {
throw new IllegalArgumentException("Failed writing summaryChartFile (" + summaryChartFile + ").", e);
}
return summaryChartFile;
}
private void determineDefaultShownScoreLevelIndex() {
defaultShownScoreLevelIndex = Integer.MAX_VALUE;
for (ProblemBenchmarkResult<Object> problemBenchmarkResult : plannerBenchmarkResult.getUnifiedProblemBenchmarkResultList()) {
if (problemBenchmarkResult.hasAnySuccess()) {
double[] winningScoreLevels = ScoreUtils.extractLevelDoubles(
problemBenchmarkResult.getWinningSingleBenchmarkResult().getAverageScore());
int[] differenceCount = new int[winningScoreLevels.length];
for (int i = 0; i < differenceCount.length; i++) {
differenceCount[i] = 0;
}
for (SingleBenchmarkResult singleBenchmarkResult : problemBenchmarkResult.getSingleBenchmarkResultList()) {
if (singleBenchmarkResult.hasAllSuccess()) {
double[] scoreLevels = ScoreUtils.extractLevelDoubles(singleBenchmarkResult.getAverageScore());
for (int i = 0; i < scoreLevels.length; i++) {
if (scoreLevels[i] != winningScoreLevels[i]) {
differenceCount[i] = differenceCount[i] + 1;
}
}
}
}
int firstInterestingLevel = differenceCount.length - 1;
for (int i = 0; i < differenceCount.length; i++) {
if (differenceCount[i] > 0) {
firstInterestingLevel = i;
break;
}
}
if (defaultShownScoreLevelIndex > firstInterestingLevel) {
defaultShownScoreLevelIndex = firstInterestingLevel;
}
}
}
}
private void writeHtmlOverviewFile() {
File benchmarkReportDirectory = plannerBenchmarkResult.getBenchmarkReportDirectory();
WebsiteResourceUtils.copyResourcesTo(benchmarkReportDirectory);
htmlOverviewFile = new File(benchmarkReportDirectory, "index.html");
Configuration freemarkerCfg = new Configuration();
freemarkerCfg.setDefaultEncoding("UTF-8");
freemarkerCfg.setLocale(locale);
freemarkerCfg.setClassForTemplateLoading(BenchmarkReport.class, "");
String templateFilename = "benchmarkReport.html.ftl";
Map<String, Object> model = new HashMap<>();
model.put("benchmarkReport", this);
model.put("reportHelper", new ReportHelper());
try (Writer writer = new OutputStreamWriter(new FileOutputStream(htmlOverviewFile), "UTF-8")) {
Template template = freemarkerCfg.getTemplate(templateFilename);
template.process(model, writer);
} catch (IOException e) {
throw new IllegalArgumentException("Can not read templateFilename (" + templateFilename
+ ") or write htmlOverviewFile (" + htmlOverviewFile + ").", e);
} catch (TemplateException e) {
throw new IllegalArgumentException("Can not process Freemarker templateFilename (" + templateFilename
+ ") to htmlOverviewFile (" + htmlOverviewFile + ").", e);
}
}
private Class getSolverRankingClass() {
if (solverRankingComparator != null) {
return solverRankingComparator.getClass();
} else if (solverRankingWeightFactory != null) {
return solverRankingWeightFactory.getClass();
} else {
return null;
}
}
}